﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading;

namespace TikTakTau_ServerProject
{
    class Server
    {
        const int threadTimeout = 100; //in ms

        object firstClient;
        object secondClient;
        Type firstClientType;
        Type secondClientType;
        string firstClientPath;
        string secondClientPath;
        int tik = 0; //number of boxes horizontally
        int tak = 0; //number of boxes vertically
        int tau = 0; //number to get in a row to win

        int[,] gameField;
        int turnNumber = 0;
        bool tieBreaker = false;

        int[] firstClientMove = { 0, 0 };
        int[] secondClientMove = { 0, 0 };

        string outputPath = "";

        public Server(string[] clients)
        {
            //DEBUG
            if (clients.Length == 0)
            {
                clients = new string[2];
                clients[0] = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Substring(6) + @"\TikTakTau_Client1.dll";
                clients[1] = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Substring(6) + @"\TikTakTau_Client2.dll";
            }
            if (clients.Length < 2)
            {
                throw new System.Exception("Must specify paths to two clients");
            }

            //assign specified parameters
            for (int i = 0; i < clients.Length; i++)
            {
                switch (i)
                {
                    case 0:
                        firstClientPath = clients[i];
                        break;
                    case 1:
                        secondClientPath = clients[i];
                        break;
                    case 2:
                        outputPath = clients[i];
                        break;
                    case 3:
                        try
                        {
                            tik = int.Parse(clients[i]);
                        }
                        catch (FormatException)
                        {
                            tik = 0;
                        }
                        break;
                    case 4:
                        try
                        {
                            tak = int.Parse(clients[i]);
                        }
                        catch (FormatException)
                        {
                            tak = 0;
                        }
                        break;
                    case 5:
                        try
                        {
                            tau = int.Parse(clients[i]);
                        }
                        catch (FormatException)
                        {
                            tau = 0;
                        }
                        break;
                    case 6:
                        string tie = clients[i];
                        if (tie.Equals("yes", StringComparison.CurrentCultureIgnoreCase)) tieBreaker = true;
                        break;
                    default:
                        break;
                }
            }

            //Load clients from reflection
            try
            {
                Assembly firstClientAssembly = Assembly.LoadFile(clients[0]);
                Assembly secondClientAssembly = Assembly.LoadFile(clients[1]);
                firstClientType = firstClientAssembly.GetType("TikTakTau_ClientProject.TikTakTau_Client");
                secondClientType = secondClientAssembly.GetType("TikTakTau_ClientProject.TikTakTau_Client");
                firstClient = Activator.CreateInstance(firstClientType);
                secondClient = Activator.CreateInstance(secondClientType);
            }
            catch (System.IO.IOException e)
            {
                throw e;
            }
            catch (System.BadImageFormatException e)
            {
                throw new System.BadImageFormatException("One of the specified paths is a bad image", e);
            }
            catch (System.MissingMethodException e)
            {
                throw new System.MissingMethodException("One of the client assemblies is missing the default constructor", e);
            }
            catch (System.Exception e)
            {
                // lots of ways to fail loading the clients
                throw e;
            }

            //assign tik,tak,tau
            Random rand = new Random((int)DateTime.Now.Ticks);
            if (tik==0)
                tik = rand.Next(4, 13); //first arg is inclusive, second arg is exclusive. Intuition!
            if (tak==0)
                tak = rand.Next(Math.Max(tik - 3, 4), Math.Min(tik + 3, 13)); // tak should be near tik, should not be larger than 12 nor smaller than 4
            if (tau==0)
                tau = rand.Next(4, (int)Math.Ceiling(Math.Min(tik,tak) * 0.76) ); //tau should be not larger than the smaller dimension. This isn't necessarily a hard requirement, but it will mean easier-to-write clients.
            //it's also good if it isn't 12 in a 12x12 game, because that's too hard to win. tau could be 8 in a 12x12, but usually will be <6
            
            //init gameField
            gameField = new int[tik, tak];
            for (int i = 0; i < tik; i++)
            {
                for (int j = 0; j < tak; j++)
                {
                    gameField[i, j] = 0;
                }
            }

            //add unusable blocks
            int numBlocks = rand.Next(0, 3);
            for (int i = 0; i <= numBlocks; i++)
            {
                gameField[rand.Next(0, tik), rand.Next(0, tak)] = 3;
            }

            //add wildcards
            int numWilds = rand.Next(0, 3);
            for (int i = 0; i <= numWilds; i++)
            {
               gameField[rand.Next(0, tik), rand.Next(0, tak)] = 4;
            }

            try
            {
                object[] firstArgs = { tik, tak, tau, 1 };
                firstClientType.InvokeMember("setGameVariables", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, System.Type.DefaultBinder, firstClient, firstArgs);

                object[] secondArgs = { tik, tak, tau, 2 };
                secondClientType.InvokeMember("setGameVariables", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, System.Type.DefaultBinder, secondClient, secondArgs);
            }
            catch (System.Exception)
            {
                // we can't tell how clients will fail, but we are executing their code. the server should not crash.
            }
        }

        public void begin()
        {
            
            int someoneWon = 0;

            while (someoneWon == 0)
            {
                Thread firstPlayerThread = new Thread(new ThreadStart(getFirstPlayerTurn));
                Thread secondPlayerThread = new Thread(new ThreadStart(getSecondPlayerTurn));

                firstPlayerThread.Start();
                Thread.Sleep(threadTimeout);
                firstPlayerThread.Abort();

                applyMove(firstClientMove, 1); //if the client has updated this value in the last 500 ms, we will do that move. if they haven't, we will do the move they tried before (no effect)
                someoneWon = checkWinnerRecursive(firstClientMove, 1);
                if (someoneWon != 0) break;
                //if this is a tiebreaker round, play two tokens at a time (but first player in first turn only plays one)
                if (tieBreaker && turnNumber > 0)
                {
                    firstPlayerThread = new Thread(new ThreadStart(getFirstPlayerTurn));
                    firstPlayerThread.Start();
                    Thread.Sleep(threadTimeout);
                    firstPlayerThread.Abort();
                    applyMove(firstClientMove, 1);
                    someoneWon = checkWinnerRecursive(firstClientMove, 1);
                    if (someoneWon != 0) break;
                }

                display();
                

                secondPlayerThread.Start();
                Thread.Sleep(threadTimeout);
                secondPlayerThread.Abort();

                applyMove(secondClientMove, 2);
                someoneWon = checkWinnerRecursive(secondClientMove, 2);
                if (someoneWon != 0) break;
                if (tieBreaker)
                {
                    secondPlayerThread = new Thread(new ThreadStart(getSecondPlayerTurn));
                    secondPlayerThread.Start();
                    Thread.Sleep(threadTimeout);
                    secondPlayerThread.Abort();
                    applyMove(secondClientMove, 2);
                    someoneWon = checkWinnerRecursive(secondClientMove, 2);
                    if (someoneWon != 0) break;
                }

                turnNumber++;

                if (turnNumber >= tik * tak / 2) someoneWon = 3;
                if (tieBreaker && turnNumber >= tik * tak / 4 + 1) someoneWon = 3;

                display();
            }
            display();
            //winner is someoneWon
            Console.WriteLine("Winner: " + someoneWon);
            Console.WriteLine();
            if (outputPath != string.Empty) //add a breakpoint to this line so you can see who wins in the console if running from Visual Studio
                System.IO.File.AppendAllText(outputPath, someoneWon + " " + tik + " " + tak + " " + tau);
        }

        void getFirstPlayerTurn()
        {
            object[] arguments = {gameField, turnNumber};
            try
            {
                firstClientMove = (int[])firstClientType.InvokeMember("playTurn", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, System.Type.DefaultBinder, firstClient, arguments);
            }
            catch (TargetInvocationException e)
            {
                Console.Out.WriteLine("Client1 crashed with \n" + e.InnerException);
            }
        }

        void getSecondPlayerTurn()
        {
            object[] arguments = { gameField, turnNumber };
            try
            {
                secondClientMove = (int[])secondClientType.InvokeMember("playTurn", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, System.Type.DefaultBinder, secondClient, arguments);
            }
            catch (TargetInvocationException e)
            {
                Console.Out.WriteLine("Client2 crashed with \n" + e.InnerException);
            }
        }

        void applyMove(int[] moveToApply, int playerNumber)
        {
            if (gameField[moveToApply[0],moveToApply[1]] == 0)
            {
                gameField[moveToApply[0],moveToApply[1]] = playerNumber;
            }
        }

        /// <summary>
        /// Unused and untested.
        /// </summary>
        /// <returns></returns>
        int checkWinnerIterative()
        {
            //check every column, check every row, check every diaganol. should only be 4MN time.
            for (int x = 0; x < tik; x++)//column
            {
                int player = 0;
                int sequential = 0;
                for (int y = 0; y < tak; y++)
                {
                    if (gameField[x,y] != player)
                    {
                        player = gameField[x, y];
                        sequential = 1;
                    }
                    else
                    {
                        sequential++;
                        if (sequential >= tau)
                        {
                            return player;
                        }
                    }
                }
            }
            for (int y = 0; y < tik; y++)//row
            {
                int player = 0;
                int sequential = 0;
                for (int x = 0; x < tak; x++)
                {
                    if (gameField[x,y] != player)
                    {
                        player = gameField[x,y];
                        sequential = 1;
                    }
                    else
                    {
                        sequential++;
                        if (sequential >= tau)
                        {
                            return player;
                        }
                    }
                }
            }
            //diaganol top-left to bottom-right
            for (int x = 0; x <= tik-tau; x++)
            {
                //diaganols starting in the first row and going toward bottom-right
                int player = 0;
                int sequential = 0;
                for (int i = 0; i < Math.Min(tik, tak); i++)
                {
                    if (gameField[x+i,i] != player)
                    {
                        player = gameField[x+i,i];
                        sequential = 1;
                    }
                    else
                    {
                        sequential++;
                        if (sequential >= tau)
                        {
                            return player;
                        }
                    }
                }
            }
            for (int y = 0; y <= tak-tau; y++)
            {
                //diaganols starting in the first column and going toward the bottom-right
                int player = 0;
                int sequential = 0;
                for (int i = 0; i < Math.Min(tik, tak); i++)
                {
                    if (gameField[i,y+i] != player)
                    {
                        player = gameField[i,y+i];
                        sequential = 1;
                    }
                    else
                    {
                        sequential++;
                        if (sequential >= tau)
                        {
                            return player;
                        }
                    }
                }
            }
            //diaganol top-right to bottom-left
            for (int x = tik; x >= 0; x--)
            {
                //diaganols starting in the first row and going toward the bottom-left
                int player = 0;
                int sequential = 0;

                for (int i = 0; i < Math.Min(tik, tak); i++)
                {
                    if (gameField[x - i,i] != player)
                    {
                        player = gameField[x - i,i];
                        sequential = 1;
                    }
                    else
                    {
                        sequential++;
                        if (sequential >= tau)
                        {
                            return player;
                        }
                    }
                }
            }
            for (int y = tak; y >= 0; y--)
            {
                //diaganols starting in the last column and going toward the bottom-left
                int player = 0;
                int sequential = 0;

                for (int i = 0; i < Math.Min(tik, tak); i++)
                {
                    if (gameField[tik-i,y-1] != player)
                    {
                        player = gameField[tik-i,y-i];
                        sequential = 1;
                    }
                    else
                    {
                        sequential++;
                        if (sequential >= tau)
                        {
                            return player;
                        }
                    }
                }
            }

            //none found in all of the above
            return 0;
        }

        /// <summary>
        /// Checks whether the specified move was a winning play.
        /// </summary>
        /// <param name="newPlay">The latest move specified in {tik,tak} form.</param>
        /// <param name="player">The player that made the move.</param>
        /// <returns>The playernumber if a winning move was played; otherwise, 0.</returns>
        int checkWinnerRecursive(int[] newPlay, int player)
        {
            //Left-Right
            if (CountAdjacent(newPlay, new int[] { 0, -1 }, player) + CountAdjacent(newPlay, new int[] { 0, 1 }, player) >= tau-1)
                return player;

            //Up-Down
            if (CountAdjacent(newPlay, new int[] { -1, 0 }, player) + CountAdjacent(newPlay, new int[] { 1, 0 }, player) >= tau-1)
                return player;

            //DiagRight
            if (CountAdjacent(newPlay, new int[] { -1, -1 }, player) + CountAdjacent(newPlay, new int[] { 1, 1 }, player) >= tau-1)
                return player;

            //DiagLeft
            if (CountAdjacent(newPlay, new int[] { -1, 1 }, player) + CountAdjacent(newPlay, new int[] { 1, -1 }, player) >= tau-1)
                return player;

            return 0;
        }

        /// <summary>
        /// Recursive method used by the <code>checkWinnerRecursive</code> method.
        /// </summary>
        /// <param name="position">The position to check specified in {tik,tak} form.</param>
        /// <param name="direction">The direction this recursive method is moving in the solar system.</param>
        /// <param name="player">The player that made the last move.</param>
        /// <returns>The number of successive planets captured by this player in this direction.</returns>
        int CountAdjacent(int[] position, int[] direction, int player)
        {
            int[] testPosition = { 0, 0 };
	        testPosition[0] = position[0] + direction[0];
	        testPosition[1] = position[1] + direction[1];

            if (testPosition[0] >= 0 && testPosition[0] < tik && testPosition[1] >= 0 && testPosition[1] < tak)
            {
                if (gameField[testPosition[0], testPosition[1]] == player || gameField[testPosition[0], testPosition[1]] == 4)
                    return (1 + CountAdjacent(testPosition, direction, player));
                else
                    return 0;
            }
            else
                return 0;
        }

        /// <summary>
        /// Displays the results of each turn in the game.
        /// </summary>
        void display()
        {
            Console.BackgroundColor = ConsoleColor.Black;
            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.Write("tik=" + tik);
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.Write(" tak=" + tak);
            Console.ForegroundColor = ConsoleColor.Green;
            Console.Write(" tau=" + tau);
            Console.ForegroundColor = ConsoleColor.White;
            Console.Write(" turn=" + turnNumber);
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.Write(" 1=" + firstClientPath.Substring(firstClientPath.IndexOf("Client")));
            Console.ForegroundColor = ConsoleColor.Red;
            Console.Write(" 2=" + secondClientPath.Substring(secondClientPath.IndexOf("Client")) + "\n");

            for (int j = 0; j < tak; j++)
            {
                for (int i = 0; i < tik; i++)
                {
                    switch (gameField[i,j])
                    {
                        case 1:
                            Console.ForegroundColor = ConsoleColor.Blue;
                            break;
                        case 2:
                            Console.ForegroundColor = ConsoleColor.Red;
                            break;
                        case 3:
                            //blocks
                            Console.ForegroundColor = ConsoleColor.Gray;
                            break;
                        case 4:
                            //wilds
                            Console.ForegroundColor = ConsoleColor.Yellow;
                            break;
                        default:
                            Console.ForegroundColor = ConsoleColor.White;
                            break;
                    }
                    Console.Write(gameField[i, j]);
                }
                Console.WriteLine();
            }
        }
    }
}
